package com.baitiaojun.security.core.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baitiaojun.common.constants.Constants;
import com.baitiaojun.common.utils.ip.IpUtils;
import com.baitiaojun.common.utils.servlet.ServletUtils;
import com.baitiaojun.common.utils.string.StringUtils;
import com.baitiaojun.redis.core.support.RedisSupport;
import com.baitiaojun.redis.core.utils.SerializableUtils;
import com.baitiaojun.security.core.domain.LoginUser;
import com.baitiaojun.security.core.service.AuthenticationService;
import com.baitiaojun.security.core.utils.TokenUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class AuthenticationServiceImpl implements AuthenticationService {

    @Autowired
    private RedisSupport redisSupport;

    @Value("${token.secret}")
    private String secret;

    @Value("${token.header}")
    private String header;

    @Value("${token.expire}")
    private String expire;

    @Value("${token.refresh.expire}")
    private String refreshExpire;

    /**
     * 解析token后获取uuid,用来向缓存中获取用户信息
     * @param uuid
     * @return
     */
    @Override
    public LoginUser getLoginUser(String uuid) {
        if (ObjectUtil.isNull(uuid)) {
            return null;
        }
        Object userInfo = redisSupport.getValue(Constants.LOGIN_TOKEN_KEY, uuid);
        if (ObjectUtil.isNull(userInfo)) {
            return null;
        }
        return SerializableUtils.parseObject(userInfo, LoginUser.class);
    }

    @Override
    public LoginUser getLoginUser(HttpServletRequest request) {
        String token = getToken(request);
        String uuid = parseToken(token);
        return getLoginUser(uuid);
    }

    /**
     * 从header中获取token
     * @param request
     * @return
     */
    @Override
    public String getToken(HttpServletRequest request) {
        String token = TokenUtils.subToken(request, header);
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        return token;
    }

    /**
     * 解析token获取payload的值, 如果过期则解析失败, 返回null
     * @param token
     * @return
     */
    @Override
    public String parseToken(final String token) {
        Claims claims = TokenUtils.parseToken(token, secret);
        if (claims == null) {
            return null;
        }
        return (String) claims.get(Constants.LOGIN_USER_KEY);
    }

    /**
     * 创建搭理人和token
     * @return
     */
    @Override
    public String createToken(String subject) {
        return TokenUtils.createToken(subject, secret, refreshExpire);
    }

    /**
     * 设置用户代理信息
     * @param token
     * @return
     */
    public LoginUser initUserAgent(String token) {
        HttpServletRequest request = ServletUtils.getRequest();
        String userAgentSign = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(userAgentSign);
        LoginUser loginUser = new LoginUser();
        String ip = IpUtils.getIp(request);
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(IpUtils.getAddressInfo(ip));
        loginUser.setOs(userAgent.getOperatingSystem().getName());
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setToken(token);
        return loginUser;
    }

    /**
     * 用户信息添加到security框架的上下文,
     * springsecurity存在一个请求线程的threadlocal, 请求结束后线程关闭,securitycontext随之消失
     * @param loginUser
     * @param request
     */
    @Override
    public void setSecurityContext(LoginUser loginUser, HttpServletRequest request) {
        //用Authentication实现类封装用户信息
        UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
//        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }

    /**
     * 如果过期时间和当前时间的差小于20分钟, 就更新缓存, 若超过20分钟就清除该用户缓存
     * @param loginUser
     * @return
     */
    @Override
    public void refresh(LoginUser loginUser) {
        Calendar afterTime = Calendar.getInstance();
        afterTime.add(Calendar.MINUTE, Integer.parseInt(expire));
        Calendar curTime = Calendar.getInstance();
        long diffTime = afterTime.getTimeInMillis() - curTime.getTimeInMillis();
        if (diffTime <= Long.parseLong(expire)) {
            addLoginUser(loginUser);
        }
    }

    /**
     * 缓存用户信息
     * @param loginUser
     */
    public void addLoginUser(LoginUser loginUser) {
        HttpServletRequest request = ServletUtils.getRequest();
        UserAgent userAgent =UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        String ip = IpUtils.getIp(request);
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(IpUtils.getAddressInfo(ip));
        loginUser.setOs(userAgent.getOperatingSystem().getName());
        long expireTime = Long.parseLong(expire);
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * 60000);
        redisSupport.addValue(Constants.LOGIN_TOKEN_KEY, loginUser.getToken(), loginUser, expireTime, TimeUnit.MINUTES);
    }
}
